#! /bin/sh

#
#   Resource Agent for managing Avago/LSI Syncro virtual drive (VD)
#   ownership. This RA uses an SCST device group (parameter) for the
#   the list of devices that should be "owned" by the node. This RA
#   was inspired by the 'syncrovd' agent from author Felix Zachlod.
#

# Initialization
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
SCST_SYSFS="/sys/kernel/scst_tgt"
BLOCK_SYSFS="/sys/block"
STORCLI_BIN="/opt/sbin/storcli64"
SG_PERSIST_BIN="/usr/bin/sg_persist"
LOCAL_NODE_ID=$(${HA_SBIN_DIR}/crm_node -i)
VD_PR_TIMEOUT=20


syncro_start() {
    # Exit immediately if configuration is not valid
    syncro_validate_all || exit ${?}

    # If resource is already running, bail out early
    if syncro_monitor; then
        ocf_log info "Resource is already running."
        return ${OCF_SUCCESS}
    fi

    # Own all of the VD's in the SCST device group (if not already)
    own_syncro_vds || return ${?}
    touch ${OCF_RESKEY_state}

    # Only return $OCF_SUCCESS if _everything_ succeeded as expected
    return ${OCF_SUCCESS}
}


syncro_stop() {
    # Exit immediately if configuration is not valid
    syncro_validate_all || exit ${?}

    # Check the current resource state
    syncro_monitor
    local rc=${?}
    case "${rc}" in
    "${OCF_SUCCESS}")
        # Currently running; normal, expected behavior
        ocf_log info "Resource is currently running."
        ;;
    "${OCF_NOT_RUNNING}")
        # Currently not running; nothing to do
        ocf_log info "Resource is already stopped."
        return ${OCF_SUCCESS}
        ;;
    esac

    # Remove the state file
    rm ${OCF_RESKEY_state}

    # Only return $OCF_SUCCESS if _everything_ succeeded as expected
    return ${OCF_SUCCESS}
}


syncro_monitor() {
    # Exit immediately if configuration is not valid
    syncro_validate_all || exit ${?}

    # If SCST isn't loaded, then we'll just say the resource isn't running
    if [ ! -d "${SCST_SYSFS}" ]; then
        ocf_log debug "It appears SCST isn't running/loaded yet..."
        return ${OCF_NOT_RUNNING}
    fi

    # Make sure the directory exists in the SCST sysfs structure
    if [ ! -d "${SCST_SYSFS}/device_groups/${OCF_RESKEY_device_group}" ]; then
        ocf_log err "The '${OCF_RESKEY_device_group}' device group" \
            "does not exist!"
        exit ${OCF_ERR_INSTALLED}
    fi

    # Determine our status using the state file
    if [ -f "${OCF_RESKEY_state}" ]; then
        # We also check that all VD's are owned by us (if running)
        own_syncro_vds || return ${?}
        return ${OCF_SUCCESS}
    else
        return ${OCF_NOT_RUNNING}
    fi
}


syncro_validate_all() {
    # Test for required binaries
    check_binary ${STORCLI_BIN}
    check_binary ${SG_PERSIST_BIN}

    # Check the state directory
    state_dir=$(dirname "${OCF_RESKEY_state}")
    touch "${state_dir}/$$"
    if [ ${?} != 0 ]; then
        ocf_log err "The state directory is not writable!"
        return ${OCF_ERR_ARGS}
    fi
    rm "${state_dir}/$$"

    # Check the SCST device group parameter
    if [ -z "${OCF_RESKEY_device_group}" ]; then
        ocf_log err "The 'device_group' parameter is not set!"
        exit ${OCF_ERR_CONFIGURED}
    fi

    # It must be good
    return ${OCF_SUCCESS}
}


syncro_meta_data() {
	cat <<-EOF
	<?xml version="1.0"?>
	<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
	<resource-agent name="syncro" version="0.1">
	  <version>0.1</version>
	  <longdesc lang="en">The "Avago/LSI Syncro" OCF resource agent for ESOS; this RA sets the VD ownership for all SCSI disks in the given SCST device group.</longdesc>
	  <shortdesc lang="en">Syncro OCF RA script for ESOS.</shortdesc>
	  <parameters>
	    <parameter name="device_group" unique="1" required="1">
	      <longdesc lang="en">The name of the SCST device group (unique to cluster configuration).</longdesc>
	      <shortdesc lang="en">The 'device_group' parameter.</shortdesc>
	      <content type="string" default="" />
	    </parameter>
	  </parameters>
	  <actions>
	    <action name="start" timeout="60" />
	    <action name="stop" timeout="20" />
	    <action name="monitor" timeout="20" />
	    <action name="validate-all" timeout="20" />
	    <action name="meta-data" timeout="5" />
	  </actions>
	</resource-agent>
	EOF
}


syncro_usage() {
    echo "usage: ${0} {start|stop|monitor|validate-all|meta-data}"
    echo ""
    echo "Expects to have a fully populated OCF RA-compliant environment set."
}


get_syncro_devs() {
    # This may get populated below
    syncro_devs=""
    # Loop over the SCST device group's devices
    scst_dev_grp_base="${SCST_SYSFS}/device_groups/${OCF_RESKEY_device_group}"
    for i in $(ls ${scst_dev_grp_base}/devices/ | grep -v mgmt); do
        # We only want the vdisk_blockio types
        if readlink ${scst_dev_grp_base}/devices/${i}/handler | \
            grep "vdisk_blockio" > /dev/null 2>&1; then
            filename="$(head -1 ${scst_dev_grp_base}/devices/${i}/filename)"
            blk_dev_path="$(readlink -f ${filename})"
            blk_dev_name="$(basename ${blk_dev_path})"
            ocf_log debug "Found a block device: ${blk_dev_name}"
            # For now, lets only look at SCSI disks
            if [[ ${blk_dev_name}" =~ "^sd[a-z].* ]]; then
                blk_dev_sysfs="${BLOCK_SYSFS}/${blk_dev_name}"
                sd_model="$(head -1 ${blk_dev_sysfs}/device/model)"
                ocf_log debug "SCSI disk model: ${sd_model}"
                # And finally, only Syncro controllers (eg, MR9286-8eHA)
                if [[ ${sd_model} =~ ^MR.*HA ]]; then
                    hctl_val="$(ls ${blk_dev_sysfs}/device/scsi_disk/ | \
                        head -1)"
                    syncro_devs="${syncro_devs} ${hctl_val}"
                fi
            fi
        fi
    done
    echo ${syncro_devs}
}


get_ctrlr_id() {
    host_num=${1}
    ctrlr_cnt=$(${STORCLI_BIN} show ctrlcount | grep "Controller Count = " |
        cut -d= -f2 | tr -d ' ')
    # These controllers always start at 0
    for j in $(seq 0 $(expr ${ctrlr_cnt} - 1)); do
        bus_num=$(${STORCLI_BIN} /c${j} show | grep "Bus Number = " |
            cut -d= -f2 | tr -d ' ')
        ocf_log debug "StorCLI bus number: ${bus_num}"
        host_num_str="$(ls /sys/bus/pci/devices/0000:0${bus_num}:00.0/ |
            grep host)"
        ocf_log debug "Linux host string: ${host_num_str}"
        if [ "x${host_num_str}" = "xhost${host_num}" ]; then
            # We found a match, we're done
            return ${j}
        fi
    done
    # If we got this far, we didn't find it
    return -1
}


is_vd_owned() {
    ctrlr=${1}
    vdrive=${2}
    show_vd_output="$(${STORCLI_BIN} /c${ctrlr}/v${vdrive} show)"
    stat_line=$(echo "${show_vd_output}" | grep "Status = " | \
        cut -d= -f2 | tr -d ' ')
    ocf_log debug "StorCLI status line: ${stat_line}"
    desc_line=$(echo "${show_vd_output}" | grep "Description = " | \
        cut -d= -f2 | tr -d ' ')
    ocf_log debug "StorCLI description line: ${desc_line}"
    if [ "x${stat_line}" != "xSuccess" -a "x${desc_line}" = "xNone" ]; then
        # Not currently the owner
        return 1
    else
        # Yes, we are the owner
        return 0
    fi
}


own_syncro_vds() {
    # Loop over all of the Syncro VD H:C:T:L values
    for k in $(get_syncro_devs); do
        if [ -n "${k}" ]; then
            ocf_log debug "Checking SCSI disk ${k}..."
            host="$(echo ${k} | cut -d: -f1)"
            channel="$(echo ${k} | cut -d: -f2)"
            target="$(echo ${k} | cut -d: -f3)"
            lun="$(echo ${k} | cut -d: -f4)"
            # Get the controller ID
            get_ctrlr_id ${host}
            ctrlr_id=${?}
            ocf_log debug "Controller ID: ${ctrlr_id}"
            if [ ${ctrlr_id} -ne -1 ]; then
                if ! is_vd_owned ${ctrlr_id} ${target}; then
                    # It appears this VD is not owned by this node, fix it
                    ocf_log info "Attempting to own VD ${target} for" \
                        "controller ${ctrlr_id}..."
                    sd_blk_dev="$(lsscsi ${k} | awk '{print $6}')"
                    ocf_log debug "${k} -> ${sd_blk_dev}"
                    # Add a new registration
                    ocf_log debug "Calling the sg_persist new-registration" \
                        "command..."
                    ${SG_PERSIST_BIN} --no-inquiry --out --register \
                        --param-rk=0 --param-sark=${LOCAL_NODE_ID} \
                        ${sd_blk_dev} 1> /dev/null
                    # Attempt to create a new PR
                    ocf_log debug "Calling the sg_persist add-reservation" \
                        "command..."
                    ${SG_PERSIST_BIN} --no-inquiry --out --reserve \
                        --param-rk=${LOCAL_NODE_ID} --prout-type=1 \
                        ${sd_blk_dev} &> /dev/null || return ${OCF_ERR_GENERIC}
                    # Keep the PR in-place until we are the VD owner
                    timer=0
                    while true; do
                        if is_vd_owned ${ctrlr_id} ${target}; then
                            break
                        fi
                        if [ ${timer} -gt ${VD_PR_TIMEOUT} ]; then
                            ocf_log err "The VD PR timeout (${VD_PR_TIMEOUT})" \
                                "for '${sd_blk_dev}' was hit!"
                            return ${OCF_ERR_GENERIC}
                        fi
                        timer=$(expr ${timer} + 2)
                        ocf_log debug "Waiting for VD ${target} to be owned..."
                        sleep 2
                    done
                    # Success (it appears), release the PR
                    ocf_log debug "Calling the sg_persist release-reservation" \
                        "command..."
                    ${SG_PERSIST_BIN} --no-inquiry --out --release \
                        --param-rk=${LOCAL_NODE_ID} --prout-type=1 \
                        ${sd_blk_dev} 1> /dev/null
                    # Get rid of the registration key
                    ocf_log debug "Calling the sg_persist remove-registration" \
                        "command..."
                    ${SG_PERSIST_BIN} --no-inquiry --out --register \
                        --param-rk=${LOCAL_NODE_ID} --param-sark=0 \
                        ${sd_blk_dev} 1> /dev/null
                fi
            fi
        fi
    done
}


# A few requirements
: ${OCF_RESKEY_CRM_meta_interval=0}
: ${OCF_RESKEY_CRM_meta_timeout=60}
: ${OCF_RESKEY_CRM_meta_globally_unique:="true"}

# Setup the state file stuff here
if [ -z "${OCF_RESKEY_state}" ]; then
    if [ "x${OCF_RESKEY_CRM_meta_globally_unique}" = "xfalse" ]; then
        state="${HA_VARRUN}syncro-${OCF_RESOURCE_INSTANCE}.state"
        OCF_RESKEY_state=$(echo ${state} | sed s/:[0-9][0-9]*\.state/.state/)
    else
        OCF_RESKEY_state="${HA_VARRUN}syncro-${OCF_RESOURCE_INSTANCE}.state"
    fi
fi

# Make sure meta-data and usage always succeed
case ${__OCF_ACTION} in
meta-data)
    syncro_meta_data
    exit ${OCF_SUCCESS}
    ;;
usage|help)
    syncro_usage
    exit ${OCF_SUCCESS}
    ;;
esac

# Anything other than meta-data and usage must pass validation
syncro_validate_all || exit ${?}

# Translate each action into the appropriate function call
case ${__OCF_ACTION} in
start)
    syncro_start
    ;;
stop)
    syncro_stop
    ;;
status|monitor)
    syncro_monitor
    ;;
validate-all)
    syncro_validate_all
    ;;
*)
    syncro_usage
    exit ${OCF_ERR_UNIMPLEMENTED}
    ;;
esac

# Log a debug message and exit
rc=${?}
ocf_log debug "${OCF_RESOURCE_INSTANCE} ${__OCF_ACTION} returned: ${rc}"
exit ${rc}

